
export class LinkedNode<N> {
  public prev: LinkedNode<N>;
  public next: LinkedNode<N>;
  constructor(public item: N) {}
}

export class LinkedList<T> {
  public head: LinkedNode<T>;
  public tail: LinkedNode<T>;
  private _length: number = 0;

  public get length(): number {
    return this._length;
  }

  constructor(item ?: T) {
    if (!item) return;
    this.append(item);
  }

  public find(item:T): LinkedNode<T>|null {
    return this.findItem(this.head, item);
  }

  private findItem(from:LinkedNode<T>, item:T):LinkedNode<T>|null {
    if (!from) return null;
    if (from.item == item) return from;
    if (!from.next) return null;
    return this.findItem(from.next, item);
  }

  public remove(item: T):boolean {
    const ok = this.removeByItem(this.head, item);
    if (ok) this._length -= 1;
    return ok;
  }

  private removeByItem(from:LinkedNode<T>, item:T):boolean {
    if (!from) return false;
    if (from.item != item) return this.removeByItem(from.next, item);
    if (from.prev) from.prev.next = from.next; else this.head = from.next;
    if (from.next) from.next.prev = from.prev; else this.tail = from.prev;
    return true;
  }

  public append(...items:T[]) {
    items.forEach((item) => {
      const node = new LinkedNode<T>(item);
      if (!this.head) {
        this.head = node;
        this.tail = node;
      } else {
        this.tail.next = node;
        node.prev = this.tail;
        this.tail = node;
      }
      this._length += 1;
    })
  }

  public toArray(): T[] {
    let node = this.head;
    const arr = new Array<T>();
    while (node) {
      arr.push(node.item);
      node = node.next;
    }
    return arr;
  }

  public each(func: (item:T, i:number)=>void) {
    let node = this.head;
    let i = 0;
    while (node) {
      func(node.item, i++)
      node = node.next;
    }
  }
}